package com.woniuxy.filters;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.woniuxy.entity.utils.ResponseEntity;
import com.woniuxy.enums.TokenEnum;
import com.woniuxy.utils.JWTUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

/**
 * @Author: 马宇航
 * @Description: 开发全局过滤器，给明天铺路,Ordered用于给一堆过滤器生效。
 * @DateTime: 22/10/13/0013 16:27
 * @Component: 成都蜗牛学苑
 **/
public class GlobalServerFilter implements GlobalFilter, Ordered {
    @Autowired
    RedisTemplate redisTemplate;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //判断是不是登录请求
        if (request.getPath().value().equals("/auth/login")) {
            //放行给过滤器链的后续过滤器
            return chain.filter(exchange);
            //判断对应的方法是否需要认证！！！
        } else if (requireAuth(request.getPath().value())) {
            String token = request.getHeaders().get("Authorization").get(0);
            String refreshToken = request.getHeaders().get("RefreshToken").get(0);
            //需要认证就要对token进行处理
            if (token.isEmpty()) {
                ResponseEntity unauthorized = new ResponseEntity(HttpStatus.UNAUTHORIZED.value());
                //定义一个封装数组  这种方式比较复杂，但是能够额外处理编码相关问题。
                byte[] data = null;
                try {
                    data = new ObjectMapper().writeValueAsString(unauthorized).getBytes(StandardCharsets.UTF_8);
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
                DataBuffer wrap = response.bufferFactory().wrap(data);
                response.getHeaders().add("Content-Type", "application/json;charset=utf-8");
                return response.writeWith(Mono.just(wrap));
            } else {
                //refreshToken 的作用：刷新token在redis的存活时间，要比token失效时间长一点
                if(!judgeRefreshTokenExpire(refreshToken)){
                    //refreshToken不存在就进入if,就需要重新登录
                    return Mono.error(new ArithmeticException("长时间未操作，请重新登录。"));
                }
                //30~60分钟以内的操作，还分为token是否过期
                if (JWTUtils.verify(token).equals(TokenEnum.TOKEN_EXPIRE)){
                    //如果这个结果是true，表示token已经过期了
                    String newToken = JWTUtils.generateToken(redisTemplate.opsForHash().get(refreshToken, "username").toString());
                    //获取响应头，并封装新的token出去。
                    response.getHeaders().add("token",newToken);
                    //无感刷新放行
                    return chain.filter(exchange);
                }
                //30分钟内的情况。
                //token有的情况，要验证token是否被篡改。
                if (JWTUtils.verify(token).equals(TokenEnum.TOKEN_SUCCESS)) {
                    //表示有token，而且校验通过。 双token认证！
                    return chain.filter(exchange);
                } else {
                    //也可以简写成这样，但是出问题了，也还是推荐使用编码自己处理。
                    return Mono.error(new Exception("对不起，token无效！"));
                }
            }
        }
        //最后的情况，不知道什么错。
        return Mono.error(new Exception("不晓得什么错！"));
    }
    /**
     * 判断刷新token有没有在redis中失效！
     * 进阶扩展：刷新token不足20分钟过期的情况下，自动续期！
     * ChangeLog : 1. 创建 (22/10/14/0014 14:28 [马宇航]);
     * @param refreshToken
     * @return boolean true表示刷新token还未过期
    */
    private boolean judgeRefreshTokenExpire(String refreshToken) {
        if (redisTemplate.hasKey(refreshToken)) {
            return true;
        }
        return false;
    }

    /**
     * 判断是否该路径是否需要 认证
     * ChangeLog : 1. 创建 (22/10/14/0014 14:18 [马宇航]);
     * @param path
     * @return boolean
    */
    private boolean requireAuth(String path) {
        //写死，那些请求都需要认证或者校验角色权限
        List<String> requireAuth = Arrays.asList(
                "/product",
                "/order"
        );
        for (String targetPath : requireAuth) {
            //
            if (path.startsWith(targetPath)){
                return true;
            }
        }
        return false;
    }

    /**
     * 值越小优先级越高！
     * ChangeLog : 1. 创建 (22/10/13/0013 16:31 [马宇航]);
     *
     * @return int
    */
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
